package poolserver

import (
	"fmt"
	"sync"
	"time"

	collector "csudata.com/zqpool/src/collector"
)

type Pool struct {
	Lock                  sync.RWMutex
	ID                    string
	FeMaxConns            int32
	FeUser                string
	FePasswd              string
	FeDbName              string
	BeRwConnCount         int32
	BeRdConnCount         int32
	BeUser                string
	BePasswd              string
	BeDbName              string
	BeConnLifeTime        int
	MsgBufSize            int
	BeRetryCnt            int32
	BeRetryInterval       int32
	BePools               map[string]*BePool /*每一个portal与后端池的对应关系*/
	LastRdPortal          string             /*记录上一次获得只读连接的portal*/
	HaAge                 int32              /*Ha的年龄，当每切换一次，此值加1*/
	DbVersion             string             /*后端数据库的版本*/
	BeMaxId               uint32             /*记录BePool中PgBackendConn的最大ID*/
	RetryCntWhenFull      int32              /*获得后端连接时，当无可用的后端连接，重试的次数*/
	RetryIntervalWhenFull int32              /*获得后端连接时，当无可用的后端连接，重试的间隔（毫秒）*/
	FeConnNums            int32              /*记录前端的连接数，主要是为了限制前端的连接数*/
}

func (pool *Pool) Init() {
	pool.BePools = make(map[string]*BePool)
	pool.BeMaxId = 0
	pool.RetryCntWhenFull = 0
	pool.RetryIntervalWhenFull = 100
}

/*记录所有的pool*/
var g_allpools = make(map[string]*Pool)
var PoolsLock sync.RWMutex

// CleanAllPools to clean all pools
func CleanAllPools() {
	g_allpools = make(map[string]*Pool)
}

func AddPool(pool *Pool) {
	poolName := fmt.Sprintf("%s.%s", pool.FeUser, pool.FeDbName)
	PoolsLock.Lock()
	g_allpools[poolName] = pool
	PoolsLock.Unlock()
}

func GetPoolByName(poolName string) *Pool {
	PoolsLock.RLock()
	pool := g_allpools[poolName]
	PoolsLock.RUnlock()
	return pool
}

// AllocConn 从连接池中获得一个连接
func (pool *Pool) AllocConn(rwState int8) *PgBackendConn {
	var cnt = 0
	var bePool *BePool
	var portal string
	var backend *PgBackendConn

	for {
		pool.Lock.RLock()
		if rwState == 1 { /*获得一个只读的连接*/
			/*先从上一次没有找过的只读节点中找，上一次找过的portal记录在pool.LastRdPortal*/
			for portal, bePool = range pool.BePools {
				if bePool.State != BE_ONLINE {
					continue
				}
				if bePool.RwState != DB_READONLY {

					continue
				}
				if portal == pool.LastRdPortal {
					continue
				}

				select {
				case backend = <-bePool.FreeChan:
					pool.LastRdPortal = portal
					pool.Lock.RUnlock()
					return backend
				default:
					backend = nil
				}
			}

			/*没有找到，则继续从上一次的只读节点中找一个连接*/
			if backend == nil && len(pool.LastRdPortal) > 0 {
				_, ok := pool.BePools[pool.LastRdPortal]
				if ok {
					bePool = pool.BePools[pool.LastRdPortal]
					if bePool.State == BE_ONLINE && bePool.RwState == DB_READONLY { /**/
						select {
						case backend = <-bePool.FreeChan:
							pool.Lock.RUnlock()
							return backend
						default:
							backend = nil
						}
					}
				} else {
					backend = nil
				}
			}

			/*如果还没有找到，则只能从主库节点中找一个连接*/
			if backend == nil {
				for portal, bePool = range pool.BePools {
					if bePool.State != BE_ONLINE {
						continue
					}
					if bePool.RwState != DB_READWRITE {
						continue
					}
					select {
					case backend = <-bePool.FreeChan:
						pool.Lock.RUnlock()
						return backend
					default:
						backend = nil
					}
				}
			}
		} else if rwState == DB_READWRITE { /*获得一个读写的连接*/
			/*从主库节点中找一个连接*/
			for portal, bePool = range pool.BePools {
				if bePool.State != BE_ONLINE {
					continue
				}
				if bePool.RwState != DB_READWRITE {
					continue
				}
				select {
				case backend = <-bePool.FreeChan:
					pool.Lock.RUnlock()
					return backend
				default:
					backend = nil
				}
			}
		}
		/*运行到这里说明，没有获得连接*/
		pool.Lock.RUnlock()
		collector.IncreaseBackendConnectionLimitReachedTimes(pool.ID)
		cnt++
		if cnt > int(pool.RetryCntWhenFull) {
			return nil
		}
		time.Sleep(time.Duration(pool.RetryIntervalWhenFull) * time.Millisecond)
	}
}

// AllocConnAndSign 从连接池中获得一个连接，并设置连接的application_name为sign
func (pool *Pool) AllocConnAndSign(rwState int8, sign string) *PgBackendConn {
	var beConn *PgBackendConn
	beConn = pool.AllocConn(rwState)
	if beConn != nil {
		setApplicationName(beConn.Conn, sign)
	}
	return beConn
}

// FreeConn 把一个连接释放回到连接池中
func (pool *Pool) FreeConn(backend *PgBackendConn) {
	pool.Lock.RLock()
	bePool := pool.BePools[backend.ConnPortal]
	pool.Lock.RUnlock()
	bePool.FreeChan <- backend
}
